package ar.edu.caece.dns.modelo.socket;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.net.SocketTimeoutException;

import org.apache.log4j.Logger;

import ar.edu.caece.dns.PropertiesReader;
import ar.edu.caece.dns.modelo.servidor.Servidor;

/**
 * Clase que espera conexiones en un puerto determinado.
 * 
 * @author Portillol
 * 
 */
public class SocketListener implements Runnable {

	private final Logger logger = Logger.getLogger(this.getClass());

	private ServerSocket serverSocket;

	private boolean escuchar;

	private Servidor servidor;

	private SocketListenerController socketListenerController;

	public Servidor getServidor() {
		return servidor;
	}

	public void setServidor(Servidor servidor) {
		this.servidor = servidor;
	}

	public SocketListener() {
		super();
	}

	public ServerSocket getServerSocket() {
		return serverSocket;
	}

	public void setServerSocket(ServerSocket serverSocket) {
		this.serverSocket = serverSocket;
	}

	public int getPuertoEscucha() {
		return serverSocket.getLocalPort();
	}

	public synchronized void setEscuchar(boolean condicion) {
		escuchar = condicion;
	}

	public synchronized boolean isEscuchar() {
		return escuchar;
	}

	public SocketListenerController getSocketListenerController() {
		return socketListenerController;
	}

	public void setSocketListenerController(
			SocketListenerController socketListenerController) {
		this.socketListenerController = socketListenerController;
	}

	public void init() {
		socketListenerController.add(this);
	}

	/**
	 * Escucha por una nueva conexion en el puerto especificado por un tiempo
	 * determinado.
	 * <p>
	 * Cuando recibe un intento de conexion, toma el socket y, junto con el
	 * servidor, se lo asigna al handler en un nuevo hilo. Luego de lo cual
	 * queda esperando nuevamente.
	 * <p>
	 * Si no recibe conexiones en un tiempo, comprueba la condicion de escucha,
	 * luego de lo cual termina o sigue ejecutando.
	 */
	@Override
	public void run() {
		logger.info("Escuchando puerto: " + serverSocket.getLocalPort());

		int timeout = Integer.valueOf(PropertiesReader.getInstancia()
				.getProperty("serverSocket.accept.timeout"));

		while (this.isEscuchar()) {

			Socket nuevoSocket = null;

			try {
				serverSocket.setSoTimeout(timeout);

				nuevoSocket = serverSocket.accept();

			} catch (SocketException se) {
				logger.debug(
						"Error al asignar timeout al server socket en puerto: "
								+ serverSocket.getLocalPort(), se);

				continue;

			} catch (SocketTimeoutException ste) {
				continue;

			} catch (IOException ioe) {
				logger.error("Conexion erronea en puerto: "
						+ serverSocket.getLocalPort(), ioe);
			}

			if (nuevoSocket != null) {
				logger.debug("Nueva conexion en puerto: "
						+ serverSocket.getLocalPort());

				SocketListenerHandler handler = new SocketListenerHandler(
						servidor, nuevoSocket);

				new Thread(handler).start();
			}

			if (!this.isEscuchar())
				break;
		}

		this.close();
	}

	/**
	 * Cierra su server socket, informando error si lo hubiere.
	 */
	public void close() {
		logger.debug("Cerrando mi puerto: " + serverSocket.getLocalPort());

		try {
			if (serverSocket != null)
				serverSocket.close();

		} catch (IOException ioe) {

			logger.error("Error cerrando mi puerto: "
					+ serverSocket.getLocalPort());

		} finally {

			serverSocket = null;
		}

	}
}
